
import java.util.*;
import java.io.*;

/**
 * This class runs a selection/projection over an input text file, and writes
 * the result to an output text file. It does this by "writing" a C++ program
 * that it compiles and executes to do the selection/projection.
 */
class Grouping {
	
	private String attsFile;
	private String writeBufferFile;
	private String readInFile;
	private String hashingFile;
	private String aggAttsFile;
	private String checkSameGroupFile;
	private String compareFile;
	private String writeOutFile;
	private String doAggFile;
	private String queryExecutableFile;

	/**
	 * attsInInputFile is the list, in order, of all of the attributes (and
	 * their types) that are present in the input file.
	 * 
	 * attsInOutputFile is the list, in order, of all of the attributes (and
	 * types) that are going to be written to the output file. This includes
	 * attributes that are the result of an aggregate compuatation.
	 * 
	 * groupingAtts is a list of all of the attributes that are used to perform
	 * the grouping.
	 * 
	 * exprsToComputeOutputAtts is a map that lists, for each of the attributes
	 * in attsInOutputFile, the expression that will be used to produce that
	 * attribute. For example, an entry in the map might be ["outAtt2", ["sum",
	 * "o_totalprice * Float (4.0)"]]. This means that outAtt2 is the result of
	 * a "sum" aggregate over the expression "o_totalprice * Float (4.0)". In
	 * place of "sum" could be a "none" or an "avg". In the case of "none", no
	 * aggregation is performed. Note that in the case of "none", the expression
	 * should be a function of only the grouping atts. In the case of "avg", an
	 * average is computed.
	 * 
	 * Then we have the name of the input file that goes to the grouping, the
	 * name of the output file that should result from the grouping, the name of
	 * the C++ compiler (this will typically be "g++" on a Linux/Mac system),
	 * and finally the path of the directory where all of the C++ files are
	 * located, and where the created C++ files are supposed to be written to
	 * (this might be something like "cppFiles/"). Make sure to put a slash on
	 * there so that we can just append the file name to the end of the
	 * directory (if you are using windows, the slash goes the other way!).
	 * 
	 */
	public Grouping(ArrayList<Attribute> attsInInputFile, ArrayList<Attribute> attsInOutputFile,
			ArrayList<String> groupingAtts, Map<String, AggFunc> exprsToComputeOutputAtts, String inFile,
			String outFile, String compiler, String cppDir) throws IOException
	{

		// We start out by writing the code to declare the atts in the input
		// record
		attsFile = cppDir + "Atts.cc";
		FileWriter fstream = new FileWriter(attsFile);
		BufferedWriter out = new BufferedWriter(fstream);
		for(Attribute i : attsInInputFile)
		{
			out.write(i.getType() + " " + i.getName() + ";\n");
		}
		out.close();
		
		// Write code for the temporary buffer if we are doing a two pass algorithm
		if (DBConfig.TWO_PASS_ALGO)
		{
			writeBufferFile = cppDir + "WriteBuffer.cc";
			fstream = new FileWriter(writeBufferFile);
			out = new BufferedWriter(fstream);
			for(Attribute i : attsInInputFile)
			{
				out.write(i.getName() + ".WriteOut(toMe);\n");
			}
			out.close();
		}
		

		// next we write the code to read in the input record
		readInFile = cppDir + "ReadIn.cc";
		fstream = new FileWriter(readInFile);
		out = new BufferedWriter(fstream);
		for(Attribute i : attsInInputFile)
		{
			out.write("res = " + i.getName() + ".ReadIn (fromMe);\n");
		}
		out.close();

		// next we write code to perform the hashing
		hashingFile = cppDir + "Hashing.cc";
		fstream = new FileWriter(hashingFile);
		out = new BufferedWriter(fstream);
		for(String i : groupingAtts)
		{
			out.write("hash = hash ^ " + i + ".GetHash ();\n");
		}
		out.write("\n");
		out.close();

		// next we write code to build the agg record
		aggAttsFile = cppDir + "AggAtts.cc";
		fstream = new FileWriter(aggAttsFile);
		out = new BufferedWriter(fstream);

		// this writes out the grouping atts
		for(String i : groupingAtts)
		{
			// find the grouping att so we can get its type
			boolean gotOne = false;
			for(Attribute j : attsInInputFile)
			{
				if(i.equals(j.getName()))
				{
					out.write(j.getType() + " ccccccc_" + j.getName() + ";\n");
					gotOne = true;
					// TODO: Come back here and make sure you can support more than one grouping attribute.
					break;
				}
			}

			if(gotOne == false)
			{
				throw new RuntimeException("error: grouping att was not found in the input");
			}
		}

		// this writes out the output atts
		for(Attribute i : attsInOutputFile)
		{
			out.write(i.getType() + " " + i.getName() + ";\n");
		}
		out.write("Int ccccccc_count;\n");
		out.close();

		// now we write code to check if the two records match
		checkSameGroupFile = cppDir + "CheckSameGroup.cc";
		fstream = new FileWriter(checkSameGroupFile);
		out = new BufferedWriter(fstream);
		out.write("return true");
		for(String i : groupingAtts)
		{
			out.write(" && ccccccc_" + i + " == aggMe." + i);
		}
		out.write(";");
		out.close();
		
		if (DBConfig.TWO_PASS_ALGO)
		{
			// Write code to compare for sorting
			compareFile = cppDir + "Compare.cc";
			fstream = new FileWriter(compareFile);
			out = new BufferedWriter(fstream);
			for(String groupingAtt : groupingAtts)
			{
				out.write("if (" + groupingAtt + " < " + " r2." + groupingAtt + ") return true;\n");
				out.write("if (" + groupingAtt + " > " + " r2." + groupingAtt + ") return false;\n");
			}
			out.close();
		}

		// and we write the code to write the output aggregate record
		writeOutFile = cppDir + "WriteOut.cc";
		fstream = new FileWriter(writeOutFile);
		out = new BufferedWriter(fstream);
		for(Attribute i : attsInOutputFile)
		{
			if(exprsToComputeOutputAtts.get(i.getName()) == null)
			{
				throw new RuntimeException("could not find an expression for att " + i.getName());
			}
			
			if(exprsToComputeOutputAtts.get(i.getName()).getFuncToRun().equals("none"))
			{
				out.write(i.getName() + ".WriteOut (toMe);\n");
			}
			else if(exprsToComputeOutputAtts.get(i.getName()).getFuncToRun().equals("sum"))
			{
				out.write(i.getName() + ".WriteOut (toMe);\n");
			}
			else if(exprsToComputeOutputAtts.get(i.getName()).getFuncToRun().equals("avg"))
			{
				out.write("(" + i.getName() + " / ccccccc_count).WriteOut (toMe);\n");
			}
			else
			{
				throw new RuntimeException("agg type must be none, sum, or avg");
			}
		}
		out.close();

		// and finally we write code to do the aggregation
		doAggFile = cppDir + "DoAgg.cc";
		fstream = new FileWriter(doAggFile);
		out = new BufferedWriter(fstream);

		// this just copies over the grouping atts
		for(String i : groupingAtts)
		{
			out.write("aggMe.ccccccc_" + i + " = " + i + ";\n");
		}

		// and now we do the aggregate atts
		for(Attribute i : attsInOutputFile)
		{

			if(exprsToComputeOutputAtts.get(i.getName()) == null)
			{
				throw new RuntimeException("found an output out without a corresponding expression");
			}

			if(exprsToComputeOutputAtts.get(i.getName()).getFuncToRun().equals("none"))
			{
				out.write("aggMe." + i.getName() + " = " + exprsToComputeOutputAtts.get(i.getName()).getExpr() + ";\n");
			}
			else
			{
				out.write("aggMe." + i.getName() + " = aggMe." + i.getName() + " + "
						+ exprsToComputeOutputAtts.get(i.getName()).getExpr() + ";\n");
			}
		}

		// and finally the count
		out.write("aggMe.ccccccc_count = aggMe.ccccccc_count + Int (1);\n");
		out.close();

		// now we compile the code
		try
		{
			Runtime rt = Runtime.getRuntime();
//			queryExecutableFile =  cppDir + "a.out";
			queryExecutableFile =  cppDir + "Grouping";
			String cppFile = (DBConfig.TWO_PASS_ALGO) ? "TPGroupBy.cc" : "GroupBy.cc";
			String cmdarr[] = { compiler, "-O3", "-o" + queryExecutableFile, "-Wno-write-strings", cppDir + cppFile };
			Process proc = rt.exec(cmdarr);
			int exitVal = proc.waitFor();
			if(exitVal != 0)
			{
				System.out.println("When I tried to compile the selection operation using " + compiler
						+ ", I got an error. " + " Try running the command:\n\n" + compiler + " -o " + queryExecutableFile
						+ " -Wno-write-strings " + cppDir + cppFile + "\n\nyourself from the command line"
						+ " to see what happened.\n");
				throw new RuntimeException("compilation failed");
			}
		}
		catch(Throwable t)
		{
			throw new RuntimeException(t);
		}

		// and we run it
		try
		{
			Runtime rt = Runtime.getRuntime();
			String cmdarr[] = { queryExecutableFile, inFile, outFile };
			Process proc = rt.exec(cmdarr);
			int exitVal = proc.waitFor();
			if(exitVal != 0)
			{
				throw new RuntimeException("Grouping: I could not manage to run the compiled program. exitVal = "
						+ exitVal);
			}
		}
		catch(Throwable t)
		{
			throw new RuntimeException(t);
		}
		
		if(DBConfig.DELETE_TEMP_CPP_FILES)
		{
			try
			{
				for(String filename : new String[] { attsFile, readInFile, hashingFile, aggAttsFile, checkSameGroupFile,
						writeOutFile, doAggFile, queryExecutableFile })
				{
					if(filename == null || filename.endsWith(".tbl"))
					{
						continue;
					}

					File f = new File(filename);
					if(f.exists() && f.isFile())
					{
						// JES OPTIONAL TODO: Add error checking
						f.delete();
					}
				}
			}
			catch(Throwable t)
			{
				throw new RuntimeException(t);
			}
		}
	}

}